package com.findsing.jetty.websocket.util;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;

import javax.websocket.DecodeException;
import javax.websocket.Decoder;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import javax.websocket.EndpointConfig;

import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Logger;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.json.JSONObject;

import com.findsing.jetty.websocket.model.Message;

public class Coder {
	private static final Logger logger = Logger.getLogger(Coder.class);
	private static ObjectMapper mapper = new ObjectMapper();
	private static final String modelPackagePath = "com.findsing.jetty.websocket.model";
	
	public static final String toJsonString(Object object, boolean wrapRootName)
	{
		mapper.configure(SerializationConfig.Feature.WRAP_ROOT_VALUE, wrapRootName);
		try {
			return mapper.writeValueAsString(object);
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			return null;
		}
	}
	public static final String toJsonString(Object object)
	{
		mapper.configure(SerializationConfig.Feature.WRAP_ROOT_VALUE, true);
		try {
			return mapper.writeValueAsString(object);
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			return null;
		}
	}
	
	public static final <T> T toObject(String jsonString)
	{
		String jsonValue = null;
		Class<T> clazz = null;
		try
		{
			JSONObject jsonObject = new JSONObject(jsonString);
			String className = (String) jsonObject.names().get(0);
			jsonValue = jsonObject.getJSONObject(className).toString();
			clazz = (Class<T>) Class.forName(modelPackagePath + "." + className);
		}
		catch(Exception e)
		{
			logger.error(e.getMessage(), e);
		}

		if(null != jsonValue && null != clazz)
		{
			try {
				return mapper.readValue(jsonValue, clazz);
			} catch (IOException e) {
				logger.error(e.getMessage(), e);
			}
		}
		else
		{
			logger.error("Unable to convert string: \"" + jsonString + "\" ");
		}
		return null;
	}
	
	public static class MessageDecoder implements Decoder.Text<Message> {
		private static final Logger logger = Logger.getLogger(MessageDecoder.class);
		
		protected String decrypt(String encoded) throws Exception
		{
			try {
				return new String(Base64.decodeBase64(encoded.getBytes(Charset.forName("UTF-8"))), "UTF-8");
			} catch (Exception e) {
				logger.error(e.getMessage() , e);
				throw new Exception(e);
			}
		}
		
		@Override
		public void init(final EndpointConfig config) {
		}

		@Override
		public Message decode(final String str) throws DecodeException {
			String jsonString;
			Message message = null;
			try {
				jsonString = decrypt(str);
				message = toObject(jsonString);
			} catch (Exception e) {
				logger.error(e.getMessage() , e);
				throw new DecodeException(str, "\"" + str + "\"" + " is not able to be decrypted", e);
			}

			return message;
		}

		@Override
		public boolean willDecode(final String str) {
			return true;
		}

		@Override
		public void destroy() {
		}
	}
	
	public static class MessageEncoder  implements Encoder.Text<Message>{
		private static final Logger logger = Logger.getLogger(MessageEncoder.class);
		
		protected String encrypt(String decoded) throws Exception
		{
			try {
				return new String(Base64.encodeBase64(decoded.getBytes()), "UTF-8");
			} catch (UnsupportedEncodingException e) {
				logger.error(e.getMessage(), e);
				throw new Exception(e);
			}
		}
		
		@Override
		public void init(final EndpointConfig config) {
		}

		@Override
		public String encode(final Message message) throws EncodeException {
			String jsonString = null;
			jsonString = toJsonString(message);
			
			if(null != jsonString)
			{
				try {
					return encrypt(jsonString);
				} catch (Exception e) {
					logger.error(e.getMessage(), e);
					throw new EncodeException(jsonString, " is unable to be encrypted", e);
				}
			}
			
			return "";
		}

		@Override
		public void destroy() {
		}
	}

}
